package com.crowntit.infrastructure.adapter.repository;

import com.alibaba.fastjson2.JSON;
import com.crowntit.domain.trade.adapter.repository.ITradeRepository;
import com.crowntit.domain.trade.model.aggregate.GroupBuyOrderAggregate;
import com.crowntit.domain.trade.model.aggregate.TradeOrderSettlementAggregate;
import com.crowntit.domain.trade.model.entity.*;
import com.crowntit.domain.trade.model.valobj.GroupBuyProgressVO;
import com.crowntit.domain.trade.model.valobj.GroupBuyTeamStatusVO;
import com.crowntit.domain.trade.model.valobj.TradeOrderStatusEnumVO;
import com.crowntit.infrastructure.DCCService;
import com.crowntit.infrastructure.dao.IGroupBuyActivityDao;
import com.crowntit.infrastructure.dao.IGroupBuyOrderDao;
import com.crowntit.infrastructure.dao.IGroupBuyOrderListDao;
import com.crowntit.infrastructure.dao.INotifyTaskDao;
import com.crowntit.infrastructure.dao.po.GroupBuyActivity;
import com.crowntit.infrastructure.dao.po.GroupBuyOrder;
import com.crowntit.infrastructure.dao.po.GroupBuyOrderList;
import com.crowntit.infrastructure.dao.po.NotifyTask;
import com.crowntit.infrastructure.redis.IRedisService;
import com.crowntit.types.common.Constants;
import com.crowntit.types.enums.NotifyTaskTypeEnumVO;
import com.crowntit.types.enums.ResponseCode;
import com.crowntit.types.exception.AppException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Slf4j
@Repository
public class TradeRepository extends AbstractRepository implements ITradeRepository {

    @Resource
    private IGroupBuyOrderListDao groupBuyOrderListDao;

    @Resource
    private IGroupBuyOrderDao groupBuyOrderDao;

    @Resource
    private IGroupBuyActivityDao groupBuyActivityDao;

    @Resource
    private INotifyTaskDao notifyTaskDao;

    @Resource
    private DCCService dccService;

    @Resource
    private IRedisService redisService;

    @Value("${spring.rabbitmq.config.producer.topic_team_success.routing_key}")
    private String mqTopic;

    @Override
    public LockPayOrderEntity queryUnPayMarketTradeOrderByOutTradeNo(String source, String channel, String userId, String outTradeNo) {
        GroupBuyOrderList groupBuyOrderListRes = groupBuyOrderListDao.queryUnPayMarketTradeOrderListByOutTradeNo(GroupBuyOrderList.builder()
                .source(source)
                .channel(channel)
                .userId(userId)
                .outTradeNo(outTradeNo)
                .build());
        if (groupBuyOrderListRes == null)
            return null;

        return LockPayOrderEntity.builder()
                .deductionPrice(groupBuyOrderListRes.getDeductionPrice())
                .outTradeNo(outTradeNo)
                .status(TradeOrderStatusEnumVO.get(groupBuyOrderListRes.getStatus())).build();
    }

    @Override
    public GroupBuyOrderListEntity queryUnPayMarketTradeOrderListByOutTradeNo(String source, String channel, String userId, String outTradeNo) {
        GroupBuyOrderList groupBuyOrderListRes = groupBuyOrderListDao.queryUnPayMarketTradeOrderListByOutTradeNo(GroupBuyOrderList.builder()
                .source(source)
                .channel(channel)
                .userId(userId)
                .outTradeNo(outTradeNo)
                .build());
        if (groupBuyOrderListRes == null)
            return null;

        return GroupBuyOrderListEntity.builder()
                .userId(groupBuyOrderListRes.getUserId())
                .teamId(groupBuyOrderListRes.getTeamId())
                .orderId(groupBuyOrderListRes.getOrderId())
                .activityId(groupBuyOrderListRes.getActivityId())
                .startTime(groupBuyOrderListRes.getStartTime())
                .endTime(groupBuyOrderListRes.getEndTime())
                .goodsId(groupBuyOrderListRes.getGoodsId())
                .source(groupBuyOrderListRes.getSource())
                .channel(groupBuyOrderListRes.getChannel())
                .originalPrice(groupBuyOrderListRes.getOriginalPrice())
                .deductionPrice(groupBuyOrderListRes.getDeductionPrice())
                .payPrice(groupBuyOrderListRes.getPayPrice())
                .status(groupBuyOrderListRes.getStatus())
                .outTradeNo(groupBuyOrderListRes.getOutTradeNo())
                .outTradeTime(groupBuyOrderListRes.getOutTradeTime())
                .bizId(groupBuyOrderListRes.getBizId())
                .build();
    }

    @Override
    public GroupBuyProgressVO queryGroupBuyProgressByTeamId(String teamId) {
        GroupBuyOrder groupBuyOrder = groupBuyOrderDao.queryGroupBuyOrderByTeamId(teamId);
        return GroupBuyProgressVO.builder()
                .completeCount(groupBuyOrder.getCompleteCount())
                .lockCount(groupBuyOrder.getLockCount())
                .targetCount(groupBuyOrder.getTargetCount())
                .build();
    }

    @Override
    public GroupBuyTeamEntity queryGroupBuyOrderTeamByTeamId(String teamId) {
        GroupBuyOrder groupBuyOrder = groupBuyOrderDao.queryGroupBuyOrderByTeamId(teamId);
        String notifyType = groupBuyOrder.getNotifyType();

        return GroupBuyTeamEntity.builder()
                .activityId(groupBuyOrder.getActivityId())
                .source(groupBuyOrder.getSource())
                .channel(groupBuyOrder.getChannel())
                .teamId(groupBuyOrder.getTeamId())
                .completeCount(groupBuyOrder.getCompleteCount())
                .targetCount(groupBuyOrder.getTargetCount())
                .lockCount(groupBuyOrder.getLockCount())
                .status(groupBuyOrder.getStatus())
                .validStartTime(groupBuyOrder.getValidStartTime())
                .validEndTime(groupBuyOrder.getValidEndTime())
                .notifyEntity(NotifyEntity.builder()
                        .notifyType(notifyType)
                        .notifyMq(notifyType.equals(NotifyTaskTypeEnumVO.MQ.getCode()) ? mqTopic : null)
                        .notifyUrl(notifyType.equals(NotifyTaskTypeEnumVO.HTTP.getCode()) ? groupBuyOrder.getNotifyUrl() : null)
                        .build())
                .build();
    }

    @Transactional(timeout = 500)
    @Override
    public LockPayOrderEntity lockMarketTradeOrder(GroupBuyOrderAggregate groupBuyOrderAggregate) {
        UserEntity userEntity = groupBuyOrderAggregate.getUserEntity();
        LockOrderActivityEntity activityEntity = groupBuyOrderAggregate.getActivityEntity();
        LockOrderDiscountEntity discountEntity = groupBuyOrderAggregate.getDiscountEntity();
        Integer userTakeOrderCount = groupBuyOrderAggregate.getUserEntity().getUserTakeOrderCount();
        String notifyUrl = groupBuyOrderAggregate.getNotifyEntity().getNotifyUrl();
        String notifyType = groupBuyOrderAggregate.getNotifyEntity().getNotifyType();

        // Group_Buy_Order: insert / update
        String teamId = activityEntity.getTeamId();
        Date validStartTime = new Date();
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(validStartTime);
        calendar.add(Calendar.MINUTE, activityEntity.getValidTime());
        Date validEndTime = calendar.getTime();

        if (StringUtils.isBlank(teamId)){
            teamId = RandomStringUtils.randomNumeric(8);

            GroupBuyOrder groupBuyOrderReq = GroupBuyOrder.builder()
                    .activityId(activityEntity.getActivityId())
                    .teamId(teamId)
                    .channel(discountEntity.getChannel())
                    .source(discountEntity.getSource())
                    .originalPrice(discountEntity.getOriginalPrice())
                    .deductionPrice(discountEntity.getDeductionPrice())
                    .payPrice(discountEntity.getDeductionPrice())
                    .targetCount(activityEntity.getTargetCount())
                    .completeCount(0)
                    .lockCount(1)
                    .status(0)
                    .notifyType(notifyType)
                    .notifyUrl(notifyUrl)
                    .validStartTime(validStartTime)
                    .validEndTime(validEndTime)
                    .build();
            groupBuyOrderDao.insertGroupBuyOrder(groupBuyOrderReq);

        } else {
            // 更新记录 - 如果更新记录不等于1，则表示拼团已满，抛出异常
            // 因为where 限制lock < target，如果修改时表中已经等于了就是改了0行，也就是拼团已满
            int updateAddTargetCount = groupBuyOrderDao.updateAddLockCountByTeamId(teamId);
            if (1 != updateAddTargetCount) {
                throw new AppException(ResponseCode.UPDATE_ZERO.getCode(), ResponseCode.UPDATE_ZERO.getInfo());
            }
        }

        // Group_Buy_Order_List: insert
        String orderId = RandomStringUtils.randomNumeric(12);
        GroupBuyOrderList groupBuyOrderListReq = GroupBuyOrderList.builder()
                .teamId(teamId)
                .orderId(orderId)
                .activityId(activityEntity.getActivityId())
                .userId(userEntity.getUserId())
                .source(discountEntity.getSource())
                .channel(discountEntity.getChannel())
                .goodsId(discountEntity.getGoodsId())
                .startTime(activityEntity.getStartTime())
                .endTime(activityEntity.getEndTime())
                .originalPrice(discountEntity.getOriginalPrice())
                .deductionPrice(discountEntity.getDeductionPrice())
                .payPrice(discountEntity.getPayPrice())
                .outTradeNo(discountEntity.getOutTradeNo())
                .status(TradeOrderStatusEnumVO.CREATE.getCode())
                .bizId(activityEntity.getActivityId() + Constants.UNDER_LINE + userEntity.getUserId() + Constants.UNDER_LINE + (userTakeOrderCount + 1))
                .build();

        try {
            // 写入拼团记录
            groupBuyOrderListDao.insertGroupBuyOrderList(groupBuyOrderListReq);
        } catch (DuplicateKeyException e) {
            throw new AppException(ResponseCode.INDEX_EXCEPTION.getCode(), ResponseCode.INDEX_EXCEPTION.getInfo());
        }

        return LockPayOrderEntity.builder()
                .outTradeNo(discountEntity.getOutTradeNo())
                .deductionPrice(discountEntity.getDeductionPrice())
                .payPrice(discountEntity.getPayPrice())
                .status(TradeOrderStatusEnumVO.CREATE)
                .validStartTime(validStartTime)
                .validEndTime(validEndTime)
                .build();
    }

    @Override
    public GroupBuyActivityEntity queryGroupBuyActivityByActivityId(Long activityId) {
//        GroupBuyActivity groupBuyActivity = groupBuyActivityDao.queryGroupBuyActivityByActivityId(activityId);
        GroupBuyActivity groupBuyActivity = getFromCacheOrDb(GroupBuyActivity.cacheRedisKey(activityId), () -> groupBuyActivityDao.queryGroupBuyActivityByActivityId(activityId));
        return GroupBuyActivityEntity.builder()
                .activityId(groupBuyActivity.getActivityId())
                .activityName(groupBuyActivity.getActivityName())
                .discountId(groupBuyActivity.getDiscountId())
                .groupType(groupBuyActivity.getGroupType())
                .takeLimitCount(groupBuyActivity.getTakeLimitCount())
                .target(groupBuyActivity.getTarget())
                .validTime(groupBuyActivity.getValidTime())
                .status(groupBuyActivity.getStatus())
                .startTime(groupBuyActivity.getStartTime())
                .endTime(groupBuyActivity.getEndTime())
                .tagId(groupBuyActivity.getTagId())
                .tagScope(groupBuyActivity.getTagScope())
                .build();
    }

    @Override
    public Integer queryUserTakeOrderCount(String userId, Long activityId) {
        GroupBuyOrderList groupBuyOrderList = GroupBuyOrderList.builder()
                .userId(userId)
                .activityId(activityId)
                .build();
        return groupBuyOrderListDao.queryUserTakeOrderCount(groupBuyOrderList);
    }

    @Transactional(timeout = 500)
    @Override
    public boolean settlementMarketPayOrder(TradeOrderSettlementAggregate tradeOrderSettlementAggregate) {
        UserEntity userEntity = tradeOrderSettlementAggregate.getUserEntity();
        GroupBuyTeamEntity teamEntity = tradeOrderSettlementAggregate.getTeamEntity();
        TradeOrderPaySuccessEntity tradeOrderPaySuccessEntity = tradeOrderSettlementAggregate.getTradeOrderPaySuccessEntity();

        // 更新orderList
        int orderListUpdated = groupBuyOrderListDao.updateOrderListStatus2COMPLETE(GroupBuyOrderList.builder()
                .channel(tradeOrderPaySuccessEntity.getChannel())
                .source(tradeOrderPaySuccessEntity.getSource())
                .outTradeNo(tradeOrderPaySuccessEntity.getOutTradeNo())
                .build());
        if (orderListUpdated != 1){
            throw new AppException(ResponseCode.UPDATE_ZERO.getCode(), ResponseCode.UPDATE_ZERO.getInfo());
        }

        // 更新order的complete count
        int orderUpdated = groupBuyOrderDao.updateGroupBuyOrderCompleteCount(teamEntity.getTeamId());
        if (orderUpdated != 1){
            log.info("order更新记录数为: {} , teamId: {} ", orderUpdated, teamEntity.getTeamId());
            throw new AppException(ResponseCode.UPDATE_ZERO.getCode(), ResponseCode.UPDATE_ZERO.getInfo());
        }

        // 检查拼团状态-如果完成，插入回调任务
        boolean isTeamComlete = false;
        Integer targetCount = teamEntity.getTargetCount();
        Integer originalCompleteCount = teamEntity.getCompleteCount();
        if (targetCount - originalCompleteCount == 1){
            isTeamComlete = true;

            int orderStatusUpdated = groupBuyOrderDao.updateOrderStatus2COMPLETE(teamEntity.getTeamId());
            if (orderStatusUpdated != 1){
                throw new AppException(ResponseCode.UPDATE_ZERO.getCode(), ResponseCode.UPDATE_ZERO.getInfo());
            }

            // 查询交易完成外部单号列表
            List<String> outTradeNoList = groupBuyOrderListDao.queryOutTradeNoListByTeamId(teamEntity.getTeamId());

            notifyTaskDao.insertNotifyTask(NotifyTask.builder()
                    .notifyType(teamEntity.getNotifyEntity().getNotifyType())
                    .notifyMq(teamEntity.getNotifyEntity().getNotifyMq())
                    .notifyUrl(teamEntity.getNotifyEntity().getNotifyUrl())
                    .activityId(teamEntity.getActivityId())
                    .teamId(teamEntity.getTeamId())
                    .notifyStatus(0)
                    .notifyCount(0)
                    .parameterJson(JSON.toJSONString(new HashMap<String, Object>() {{
                        put("teamId", teamEntity.getTeamId());
                        put("outTradeNoList", outTradeNoList);
                        put("status", "success");
                    }}))
                    .build());
        }

        return isTeamComlete;
    }

    @Override
    public boolean isScIntercept(String source, String channel) {
        return dccService.isBlackIntercept(source, channel);
    }

    @Override
    public List<NotifyTaskEntity> queryUnExecutedNotifyTaskByTeamId(String teamId) {
        NotifyTask notifyTask = notifyTaskDao.queryUnExecutedNotifyTaskByTeamId(teamId);
        if (notifyTask == null)
            return new ArrayList<>();

        NotifyTaskEntity build = NotifyTaskEntity.builder()
                .activityId(notifyTask.getActivityId())
                .notifyType(notifyTask.getNotifyType())
                .notifyMq(notifyTask.getNotifyMq())
                .notifyUrl(notifyTask.getNotifyUrl())
                .notifyCount(notifyTask.getNotifyCount())
                .parameterJson(notifyTask.getParameterJson())
                .teamId(notifyTask.getTeamId())
                .build();
        return Collections.singletonList(build);
    }

    @Override
    public List<NotifyTaskEntity> queryUnExecutedNotifyTaskList() {
        List<NotifyTask> notifyTaskList = notifyTaskDao.queryUnExecutedNotifyTaskList();
        if (notifyTaskList.isEmpty()) return new ArrayList<>();

        List<NotifyTaskEntity> notifyTaskEntities = new ArrayList<>();
        for (NotifyTask notifyTask : notifyTaskList) {
            notifyTaskEntities.add(NotifyTaskEntity.builder()
                    .notifyType(notifyTask.getNotifyType())
                    .notifyMq(notifyTask.getNotifyMq())
                    .notifyUrl(notifyTask.getNotifyUrl())
                    .notifyCount(notifyTask.getNotifyCount())
                    .parameterJson(notifyTask.getParameterJson())
                    .activityId(notifyTask.getActivityId())
                    .teamId(notifyTask.getTeamId())
                    .build());
        }
        return notifyTaskEntities;
    }

    @Override
    public int updateNotifyTaskStatus2SUCCESS(String teamId) {
        return notifyTaskDao.updateNotifyTaskStatus2SUCCESS(teamId);
    }

    @Override
    public int updateNotifyTaskStatus2ERROR(String teamId) {
        return notifyTaskDao.updateNotifyTaskStatus2ERROR(teamId);
    }

    @Override
    public int updateNotifyTaskStatus2RETRY(String teamId) {
        return notifyTaskDao.updateNotifyTaskStatus2RETRY(teamId);
    }

    @Override
    public boolean occupyTeamStockKey(String teamStockKey, String recoveryTeamStockKey, Integer target, Integer validTime) {
        // 获取回复值
        Long recoveryCount = redisService.getAtomicLong(recoveryTeamStockKey);
        recoveryCount = recoveryCount == null ? 0 : recoveryCount;

        // 自增和对比
        long occupyCount = redisService.incr(teamStockKey) + 1;
        if (occupyCount > target + recoveryCount){
            redisService.setAtomicLong(teamStockKey, target);
            return false;
        }

        // 加锁
        String lockKey = teamStockKey + Constants.UNDER_LINE + occupyCount;
        Boolean isLock = redisService.setNx(lockKey, validTime + 60, TimeUnit.MINUTES);

        if(!isLock){
            log.info("组队库存加锁失败 {}", lockKey);
        }
        return isLock;
    }

    @Override
    public void recoveryTeamStockKey(String recoveryTeamStockKey, Integer validTime) {
        if (StringUtils.isBlank(recoveryTeamStockKey)) return;

        redisService.incr(recoveryTeamStockKey);
    }
}
